/*
 * @copyright Copyright (c) 2010 Laboratório de Educação Cerebral. (http://www.educacaocerebral.com.br)
 *
 * This file is part of SoftVFC.
 *
 * SoftVFC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SoftVFC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SoftVFC.  If not, see <http://www.gnu.org/licenses/>.
 *
 * *********************
 *
 * Este arquivo é parte do programa SoftVFC.
 *
 * SoftVFC é um software livre; você pode redistribui-lo e/ou
 * modifica-lo dentro dos termos da Licença Pública Geral GNU como
 * publicada pela Fundação do Software Livre (FSF); na versão 3 da
 * Licença.
 *
 * Este programa é distribuido na esperança que possa ser util,
 * mas SEM NENHUMA GARANTIA; sem uma garantia implicita de ADEQUAÇÂO a qualquer
 * MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a
 * Licença Pública Geral GNU para maiores detalhes.
 *
 * Você deve ter recebido uma cópia da Licença Pública Geral GNU
 * junto com este programa, se não, acesse no website oficial:
 * http://www.gnu.org/licenses/gpl.html
 *
 */
package org.softvfc.daq.trace;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.sound.sampled.*;
import javax.swing.Timer;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.NbPreferences;
import org.openide.windows.WindowManager;
import org.softvfc.daq.options.DriversPanel;
import org.softvfc.dialog.notify.NotifyDialog;
import org.softvfc.dialog.notify.NotifyType;
import org.softvfc.tablehandler.window.TableTopComponent;

/**
 *
 * @author Diego Schmaedech Martins (schmaedech@gmail.com)
 * @version 29/07/2010
 */
public class CaptureWaveHeart extends javax.swing.JPanel {


    Capture capture = new Capture(); 
    SamplingGraph samplingGraph = new SamplingGraph();
    FormatControls formatControls = new FormatControls();

    double duration, seconds;
    double limitHigh, limitLow;

    Timer timer;
    ActionListener taskPerformer = new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent evt) {
            try{
                jtfLeftBC.setText(String.valueOf(samplingGraph.getLeftBC()));
                jtfRightBC.setText(String.valueOf(samplingGraph.getRightBC()));
            }catch(Exception ex){

            }


        }


    };
   
    /** Creates new form CaptureWaveHeart */
    public CaptureWaveHeart() {
        initComponents();
        samplingGraph = new SamplingGraph(); 
        jpSampling.setLayout( new GridLayout(0,1) );
        jpSampling.add( samplingGraph ); 
        jtbStartStop.setSelected(NbPreferences.forModule(CaptureWaveHeart.class).getBoolean("PlayOnLoad", true));
        samplingGraph.setRedLimit(NbPreferences.forModule(CaptureWaveHeart.class).getInt("Threshold", 10));

        jsThreshold.setValue(samplingGraph.getRedLimit()); 
        if(jtbStartStop.isSelected()){
            capture.start();
            samplingGraph.start();
        }
        int delay = 1000; //milliseconds

        timer = new Timer(delay, taskPerformer);
        timer.start();

        Preferences pref = NbPreferences.forModule(DriversPanel.class);

        pref.addPreferenceChangeListener(new PreferenceChangeListener() {
            @Override
            public void preferenceChange(PreferenceChangeEvent evt) {
                if (evt.getKey().equals("Default")) {
                    //jTextField1.setText(evt.getNewValue());
                    //capture.stop();
                   jtbStartStop.doClick();
                }
            }
        });


       
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jToolBar1 = new javax.swing.JToolBar();
        jtbStartStop = new javax.swing.JToggleButton();
        jSeparator3 = new javax.swing.JToolBar.Separator();
        jLabel1 = new javax.swing.JLabel();
        jtfLeftBC = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        jtfRightBC = new javax.swing.JTextField();
        jSeparator2 = new javax.swing.JToolBar.Separator();
        jbRRLeftTable = new javax.swing.JButton();
        jbRRRightTable = new javax.swing.JButton();
        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jbCreateDataSource = new javax.swing.JButton();
        jpSampling = new javax.swing.JPanel();
        jPanel1 = new javax.swing.JPanel();
        jsThreshold = new javax.swing.JSlider();

        setBackground(new java.awt.Color(254, 254, 254));

        jToolBar1.setFloatable(false);
        jToolBar1.setRollover(true);

        jtbStartStop.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/softvfc/daq/trace/media-playback-start.png"))); // NOI18N
        jtbStartStop.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jtbStartStop.text")); // NOI18N
        jtbStartStop.setToolTipText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jtbStartStop.toolTipText")); // NOI18N
        jtbStartStop.setFocusable(false);
        jtbStartStop.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jtbStartStop.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/softvfc/daq/trace/media-playback-start.png"))); // NOI18N
        jtbStartStop.setRolloverSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/softvfc/daq/trace/media-playback-stop.png"))); // NOI18N
        jtbStartStop.setSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/softvfc/daq/trace/media-playback-stop.png"))); // NOI18N
        jtbStartStop.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jtbStartStop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jtbStartStopActionPerformed(evt);
            }
        });
        jToolBar1.add(jtbStartStop);
        jToolBar1.add(jSeparator3);

        jLabel1.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jLabel1.text")); // NOI18N
        jToolBar1.add(jLabel1);

        jtfLeftBC.setColumns(5);
        jtfLeftBC.setEditable(false);
        jtfLeftBC.setForeground(java.awt.Color.green);
        jtfLeftBC.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jtfLeftBC.text")); // NOI18N
        jToolBar1.add(jtfLeftBC);

        jLabel2.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jLabel2.text")); // NOI18N
        jToolBar1.add(jLabel2);

        jtfRightBC.setColumns(5);
        jtfRightBC.setEditable(false);
        jtfRightBC.setForeground(java.awt.Color.blue);
        jtfRightBC.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jtfRightBC.text")); // NOI18N
        jToolBar1.add(jtfRightBC);
        jToolBar1.add(jSeparator2);

        jbRRLeftTable.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jbRRLeftTable.text")); // NOI18N
        jbRRLeftTable.setFocusable(false);
        jbRRLeftTable.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jbRRLeftTable.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jbRRLeftTable.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbRRLeftTableActionPerformed(evt);
            }
        });
        jToolBar1.add(jbRRLeftTable);

        jbRRRightTable.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jbRRRightTable.text")); // NOI18N
        jbRRRightTable.setFocusable(false);
        jbRRRightTable.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jbRRRightTable.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jbRRRightTable.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbRRRightTableActionPerformed(evt);
            }
        });
        jToolBar1.add(jbRRRightTable);

        jButton1.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jButton1.text")); // NOI18N
        jButton1.setFocusable(false);
        jButton1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jButton1.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jToolBar1.add(jButton1);

        jButton2.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jButton2.text")); // NOI18N
        jButton2.setFocusable(false);
        jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jToolBar1.add(jButton2);

        jbCreateDataSource.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/softvfc/daq/trace/x-office-spreadsheet.png"))); // NOI18N
        jbCreateDataSource.setText(org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jbCreateDataSource.text")); // NOI18N
        jbCreateDataSource.setFocusable(false);
        jbCreateDataSource.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jbCreateDataSource.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jbCreateDataSource.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jbCreateDataSourceActionPerformed(evt);
            }
        });
        jToolBar1.add(jbCreateDataSource);

        jpSampling.setBackground(new java.awt.Color(254, 254, 254));
        jpSampling.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(141, 171, 227)));
        jpSampling.setPreferredSize(new java.awt.Dimension(628, 100));

        javax.swing.GroupLayout jpSamplingLayout = new javax.swing.GroupLayout(jpSampling);
        jpSampling.setLayout(jpSamplingLayout);
        jpSamplingLayout.setHorizontalGroup(
            jpSamplingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 706, Short.MAX_VALUE)
        );
        jpSamplingLayout.setVerticalGroup(
            jpSamplingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 141, Short.MAX_VALUE)
        );

        jPanel1.setBackground(new java.awt.Color(254, 254, 254));
        jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(CaptureWaveHeart.class, "CaptureWaveHeart.jPanel1.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 1, 10))); // NOI18N
        jPanel1.setPreferredSize(new java.awt.Dimension(100, 100));
        jPanel1.setLayout(new java.awt.GridBagLayout());

        jsThreshold.setFont(new java.awt.Font("DejaVu Sans", 0, 10));
        jsThreshold.setMajorTickSpacing(10);
        jsThreshold.setMinimum(1);
        jsThreshold.setMinorTickSpacing(1);
        jsThreshold.setOrientation(javax.swing.JSlider.VERTICAL);
        jsThreshold.setPaintTicks(true);
        jsThreshold.setValue(10);
        jsThreshold.setMinimumSize(new java.awt.Dimension(32, 1));
        jsThreshold.setPreferredSize(new java.awt.Dimension(32, 50));
        jsThreshold.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                thresholdChange(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.ipady = 101;
        jPanel1.add(jsThreshold, gridBagConstraints);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, 794, Short.MAX_VALUE)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(jpSampling, javax.swing.GroupLayout.DEFAULT_SIZE, 708, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 143, Short.MAX_VALUE)
                    .addComponent(jpSampling, javax.swing.GroupLayout.DEFAULT_SIZE, 143, Short.MAX_VALUE)))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void thresholdChange(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_thresholdChange
        samplingGraph.setRedLimit(jsThreshold.getValue());
        NbPreferences.forModule(CaptureWaveHeart.class).putInt("Threshold", jsThreshold.getValue());
    }//GEN-LAST:event_thresholdChange

    private void jbRRLeftTableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbRRLeftTableActionPerformed
       
    }//GEN-LAST:event_jbRRLeftTableActionPerformed

    private void jbRRRightTableActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbRRRightTableActionPerformed
        
    }//GEN-LAST:event_jbRRRightTableActionPerformed

    private void jtbStartStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jtbStartStopActionPerformed

        NbPreferences.forModule(CaptureWaveHeart.class).putBoolean("PlayOnLoad", jtbStartStop.isSelected());
        if(jtbStartStop.isSelected()){
            capture.start();
            samplingGraph.start();
        }else{
            capture.stop();
            samplingGraph.stop();
        }

    }//GEN-LAST:event_jtbStartStopActionPerformed

    private void jbCreateDataSourceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jbCreateDataSourceActionPerformed
         WindowManager.getDefault().invokeWhenUIReady(new Runnable() {

            @Override
            public void run() {
                TableTopComponent[] tableComponent = new TableTopComponent[2];
                tableComponent[0] = new TableTopComponent();
                tableComponent[0].changeTableModel(samplingGraph.getSamplingRightTableModel());               
                tableComponent[0].setDisplayName("Table Right RR " + tableComponent[0].getIndex());
                tableComponent[0].open();
                tableComponent[0].requestActive();

                tableComponent[1] = new TableTopComponent();
                tableComponent[1].changeTableModel(samplingGraph.getSamplingLeftTableModel());
                tableComponent[1].setDisplayName("Table Left RR " + tableComponent[1].getIndex());
                tableComponent[1].open();
                tableComponent[1].requestActive();
            }
        });
    }//GEN-LAST:event_jbCreateDataSourceActionPerformed

    

    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JToolBar.Separator jSeparator2;
    private javax.swing.JToolBar.Separator jSeparator3;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JButton jbCreateDataSource;
    private javax.swing.JButton jbRRLeftTable;
    private javax.swing.JButton jbRRRightTable;
    private javax.swing.JPanel jpSampling;
    private javax.swing.JSlider jsThreshold;
    private javax.swing.JToggleButton jtbStartStop;
    private javax.swing.JTextField jtfLeftBC;
    private javax.swing.JTextField jtfRightBC;
    // End of variables declaration//GEN-END:variables


    class Capture implements Runnable {

        TargetDataLine line;
        DataLine.Info info;
        AudioFormat format;
        Thread thread;
        AudioInputStream audioInputStream;
        boolean defaultLine = true;
         
        public void start() { 

            defaultLine = NbPreferences.forModule(DriversPanel.class).getBoolean("Default", true);
            thread = new Thread(this);
            thread.setName("capture");
            thread.start();

        }

        public void stop() {
             thread = null;
        }

        @SuppressWarnings("static-access")
        @Override
        public void run() {

            duration = 0;
            audioInputStream = null;

            if(defaultLine){
                format = formatControls.getDefaultAudioFormat();
                info = new DataLine.Info(TargetDataLine.class, format);
                
            }else{
                try{
                    int storeMixerDriver = NbPreferences.forModule(DriversPanel.class).getInt("MixerDriver", 0);
                    Mixer mixer = AudioSystem.getMixer(AudioSystem.getMixerInfo()[storeMixerDriver]);
                    int storeTargetLine = NbPreferences.forModule(DriversPanel.class).getInt("TargetLine", 0);
                    info = ((DataLine.Info)mixer.getTargetLineInfo()[storeTargetLine]);
                    int storeAudioFormat = NbPreferences.forModule(DriversPanel.class).getInt("AudioFormat", 0);
                    format = info.getFormats()[storeAudioFormat];
                }catch(Exception ex){
                    error(ex.getMessage());

                }
            }

            if (!AudioSystem.isLineSupported(info)) {                

                error("This Line is not supported by Audio System!");
                return;
            }
            try {
                line = (TargetDataLine) AudioSystem.getLine(info);
                line.open(format, line.getBufferSize());
            } catch (LineUnavailableException ex) {
                error(ex.getMessage());
                return;
            } catch (SecurityException ex) {
                error(ex.getMessage());
                return;
            } catch (Exception ex) {
                error(ex.getMessage());
                return;
            }
            

            // play back the captured audio data
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int frameSizeInBytes = format.getFrameSize();
            int bufferLengthInFrames = line.getBufferSize() / 8;
            int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
            byte[] data = new byte[bufferLengthInBytes];
            int numBytesRead;

            line.start();

            while (thread != null) {
                    if ((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1) {
                        break;
                    }
                    out.write(data, 0, numBytesRead);

                    //this is my magic modification to run
                    if(out.size() > 16384*1 ){
                     // we reached the end of the stream.  stop and close the line.
                        byte[] audioBytes = out.toByteArray();
                        ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
                        audioInputStream = new AudioInputStream(bais, format, audioBytes.length / frameSizeInBytes );

                        long milliseconds = (long)((audioInputStream.getFrameLength() * 1000) / format.getFrameRate());
                        duration = milliseconds;
                        samplingGraph.createWaveForm(audioBytes,audioInputStream,duration);
                        try {
                            audioInputStream.reset();
                            out.reset();

                        } catch (Exception ex) {
                            error(ex.getMessage());
                            return;
                        }

                    }
            }

            line.stop();
            line.close();
            line = null;
            try {
                out.flush();
                out.close();
            } catch (IOException ex) {
                error(ex.getMessage());

            }

            byte[] audioBytes = out.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
            audioInputStream = new AudioInputStream(bais, format, audioBytes.length / frameSizeInBytes);

            long milliseconds = (long)((audioInputStream.getFrameLength() * 1000) / format.getFrameRate());
            duration = milliseconds;

            try {
                audioInputStream.reset();
            } catch (Exception ex) {
                error(ex.getMessage());
                return;
            }

        }
  
        private void error(final String msg){
            NotifyDialog.notify("Audio System Error!", "This Line is not supported by Audio System!", NotifyType.ERROR_MESSAGE, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent evt){

                        NotifyDescriptor nd = new NotifyDescriptor(
                                msg,
                                "Exception message!",
                                NotifyDescriptor.PLAIN_MESSAGE,
                                NotifyDescriptor.ERROR_MESSAGE,
                                null,
                                NotifyDescriptor.OK_CANCEL_OPTION
                        );
                        DialogDisplayer.getDefault().notify(nd);
                        jtbStartStop.doClick();
                    }
                });
        }
    } // End class Capture

}
